﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;

namespace KeyRemapper
{
    public class KeyboardHook
    {
        private IntPtr _hookId = IntPtr.Zero;

        public KeyboardHook()
        {
        }

        public void Hook()
        {
            using (Process me = Process.GetCurrentProcess())
            using (ProcessModule module = me.MainModule)
            {
                if (_hookId != IntPtr.Zero)
                    throw new Exception("Hook already set!");

                _hookId = SetWindowsHookEx(WH_KEYBOARD_LL, HookCallback, GetModuleHandle(module.ModuleName), 0);

                if (_hookId == IntPtr.Zero)
                    throw new Exception("Unable to set hook!");
            }
        }
        private IntPtr HookCallback(int nCode, IntPtr wParam, IntPtr lParam)
        {
            try
            {
                if (nCode >= 0 && wParam == (IntPtr)WM_KEYDOWN)
                {
                    Keys key = (Keys)Marshal.ReadInt32(lParam);
                    int flags = Marshal.ReadInt32(new IntPtr(lParam.ToInt32() + 8));
                    bool injected = ((flags & 0x00000010) == 0x00000010);

                    if (!injected)
                    {
                        bool block = false;
                        
                        OnKeyPressed(key, ref block);

                        if (block)
                            return new IntPtr(1);
                    }
                }
            }
            catch(Exception ex)
            {
                Debug.WriteLine(ex.ToString());
            }
            return CallNextHookEx(_hookId, nCode, wParam, lParam);
        }
        public void Unhook()
        {
            if (_hookId != IntPtr.Zero)
            {
                UnhookWindowsHookEx(_hookId);
                _hookId = IntPtr.Zero;
            }
        }

        public delegate void OnKeyPressedDelegate(Keys k, ref bool block);
        public event OnKeyPressedDelegate OnKeyPressed = null;

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(int idHook,
            LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode,
            IntPtr wParam, IntPtr lParam);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr GetModuleHandle(string lpModuleName);

        private const int WH_KEYBOARD_LL = 13;
        private const int WM_KEYDOWN = 0x0100;

        private delegate IntPtr LowLevelKeyboardProc(
            int nCode, IntPtr wParam, IntPtr lParam);
    }
}
